// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 * Copyright (C) 1996-2015, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.dev.test.format;

import com.ibm.icu.dev.test.CoreTestFmwk;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.DisplayContext;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.util.ULocale;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.Locale;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * This does not test lenient parse mode, since testing the default implementation introduces a
 * dependency on collation. See RbnfLenientScannerTest.
 */
@RunWith(JUnit4.class)
public class RbnfTest extends CoreTestFmwk {
    static String fracRules =
            "%main:\n"
                    +
                    // this rule formats the number if it's 1 or more.  It formats
                    // the integral part using a DecimalFormat ("#,##0" puts
                    // thousands separators in the right places) and the fractional
                    // part using %%frac.  If there is no fractional part, it
                    // just shows the integral part.
                    "    x.0: <#,##0<[ >%%frac>];\n"
                    +
                    // this rule formats the number if it's between 0 and 1.  It
                    // shows only the fractional part (0.5 shows up as "1/2," not
                    // "0 1/2")
                    "    0.x: >%%frac>;\n"
                    +
                    // the fraction rule set.  This works the same way as the one in the
                    // preceding example: We multiply the fractional part of the number
                    // being formatted by each rule's base value and use the rule that
                    // produces the result closest to 0 (or the first rule that produces 0).
                    // Since we only provide rules for the numbers from 2 to 10, we know
                    // we'll get a fraction with a denominator between 2 and 10.
                    // "<0<" causes the numerator of the fraction to be formatted
                    // using numerals
                    "%%frac:\n"
                    + "    2: 1/2;\n"
                    + "    3: <0</3;\n"
                    + "    4: <0</4;\n"
                    + "    5: <0</5;\n"
                    + "    6: <0</6;\n"
                    + "    7: <0</7;\n"
                    + "    8: <0</8;\n"
                    + "    9: <0</9;\n"
                    + "   10: <0</10;\n";

    @Test
    public void TestCoverage() {
        String durationInSecondsRules =
                // main rule set for formatting with words
                "%with-words:\n"
                        // take care of singular and plural forms of "second"
                        + "    0 seconds; 1 second; =0= seconds;\n"
                        // use %%min to format values greater than 60 seconds
                        + "    60/60: <%%min<[, >>];\n"
                        // use %%hr to format values greater than 3,600 seconds
                        // (the ">>>" below causes us to see the number of minutes
                        // when when there are zero minutes)
                        + "    3600/60: <%%hr<[, >>>];\n"
                        // this rule set takes care of the singular and plural forms
                        // of "minute"
                        + "%%min:\n"
                        + "    0 minutes; 1 minute; =0= minutes;\n"
                        // this rule set takes care of the singular and plural forms
                        // of "hour"
                        + "%%hr:\n"
                        + "    0 hours; 1 hour; =0= hours;\n"

                        // main rule set for formatting in numerals
                        + "%in-numerals:\n"
                        // values below 60 seconds are shown with "sec."
                        + "    =0= sec.;\n"
                        // higher values are shown with colons: %%min-sec is used for
                        // values below 3,600 seconds...
                        + "    60: =%%min-sec=;\n"
                        // ...and %%hr-min-sec is used for values of 3,600 seconds
                        // and above
                        + "    3600: =%%hr-min-sec=;\n"
                        // this rule causes values of less than 10 minutes to show without
                        // a leading zero
                        + "%%min-sec:\n"
                        + "    0: :=00=;\n"
                        + "    60/60: <0<>>;\n"
                        // this rule set is used for values of 3,600 or more.  Minutes are always
                        // shown, and always shown with two digits
                        + "%%hr-min-sec:\n"
                        + "    0: :=00=;\n"
                        + "    60/60: <00<>>;\n"
                        + "    3600/60: <#,##0<:>>>;\n"
                        // the lenient-parse rules allow several different characters to be used
                        // as delimiters between hours, minutes, and seconds
                        + "%%lenient-parse:\n"
                        + "    & : = . = ' ' = -;\n";

        // extra calls to boost coverage numbers
        RuleBasedNumberFormat fmt0 = new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
        RuleBasedNumberFormat fmt1 = fmt0.clone();
        RuleBasedNumberFormat fmt2 = new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
        if (!fmt0.equals(fmt0)) {
            errln("self equality fails");
        }
        if (!fmt0.equals(fmt1)) {
            errln("clone equality fails");
        }
        if (!fmt0.equals(fmt2)) {
            errln("duplicate equality fails");
        }
        String str = fmt0.toString();
        logln(str);

        RuleBasedNumberFormat fmt3 = new RuleBasedNumberFormat(durationInSecondsRules);

        if (fmt0.equals(fmt3)) {
            errln("nonequal fails");
        }
        if (!fmt3.equals(fmt3)) {
            errln("self equal 2 fails");
        }
        str = fmt3.toString();
        logln(str);

        String[] names = fmt3.getRuleSetNames();

        try {
            fmt3.setDefaultRuleSet(null);
            fmt3.setDefaultRuleSet("%%foo");
            errln("sdrf %%foo didn't fail");
        } catch (Exception e) {
            logln("Got the expected exception");
        }

        try {
            fmt3.setDefaultRuleSet("%bogus");
            errln("sdrf %bogus didn't fail");
        } catch (Exception e) {
            logln("Got the expected exception");
        }

        try {
            str = fmt3.format(2.3, names[0]);
            logln(str);
            str = fmt3.format(2.3, "%%foo");
            errln("format double %%foo didn't fail");
        } catch (Exception e) {
            logln("Got the expected exception");
        }

        try {
            str = fmt3.format(123L, names[0]);
            logln(str);
            str = fmt3.format(123L, "%%foo");
            errln("format double %%foo didn't fail");
        } catch (Exception e) {
            logln("Got the expected exception");
        }

        RuleBasedNumberFormat fmt4 = new RuleBasedNumberFormat(fracRules, Locale.ENGLISH);
        RuleBasedNumberFormat fmt5 = new RuleBasedNumberFormat(fracRules, Locale.ENGLISH);
        str = fmt4.toString();
        logln(str);
        if (!fmt4.equals(fmt5)) {
            errln("duplicate 2 equality failed");
        }
        str = fmt4.format(123L);
        logln(str);
        try {
            Number num = fmt4.parse(str);
            logln(num.toString());
        } catch (Exception e) {
            errln("parse caught exception");
        }

        str = fmt4.format(.000123);
        logln(str);
        try {
            Number num = fmt4.parse(str);
            logln(num.toString());
        } catch (Exception e) {
            errln("parse caught exception");
        }

        str = fmt4.format(456.000123);
        logln(str);
        try {
            Number num = fmt4.parse(str);
            logln(num.toString());
        } catch (Exception e) {
            errln("parse caught exception");
        }
    }

    @Test
    public void TestUndefinedSpellout() {
        Locale greek = new Locale("el", "", "");
        RuleBasedNumberFormat[] formatters = {
            new RuleBasedNumberFormat(greek, RuleBasedNumberFormat.SPELLOUT),
            new RuleBasedNumberFormat(greek, RuleBasedNumberFormat.ORDINAL),
            new RuleBasedNumberFormat(greek, RuleBasedNumberFormat.DURATION),
        };

        String[] data = {
            "0",
            "1",
            "15",
            "20",
            "23",
            "73",
            "88",
            "100",
            "106",
            "127",
            "200",
            "579",
            "1,000",
            "2,000",
            "3,004",
            "4,567",
            "15,943",
            "105,000",
            "2,345,678",
            "-36",
            "-36.91215",
            "234.56789"
        };

        NumberFormat decFormat = NumberFormat.getInstance(Locale.US);
        for (int j = 0; j < formatters.length; ++j) {
            com.ibm.icu.text.NumberFormat formatter = formatters[j];
            logln("formatter[" + j + "]");
            for (int i = 0; i < data.length; ++i) {
                try {
                    String result = formatter.format(decFormat.parse(data[i]));
                    logln("[" + i + "] " + data[i] + " ==> " + result);
                } catch (Exception e) {
                    errln(
                            "formatter["
                                    + j
                                    + "], data["
                                    + i
                                    + "] "
                                    + data[i]
                                    + " threw exception "
                                    + e.getMessage());
                }
            }
        }
    }

    /** Perform a simple spot check on the English spellout rules */
    @Test
    public void TestEnglishSpellout() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(Locale.US, RuleBasedNumberFormat.SPELLOUT);
        String[][] testData = {
            {"1", "one"},
            {"15", "fifteen"},
            {"20", "twenty"},
            {"23", "twenty-three"},
            {"73", "seventy-three"},
            {"88", "eighty-eight"},
            {"100", "one hundred"},
            {"106", "one hundred six"},
            {"127", "one hundred twenty-seven"},
            {"200", "two hundred"},
            {"579", "five hundred seventy-nine"},
            {"1,000", "one thousand"},
            {"2,000", "two thousand"},
            {"3,004", "three thousand four"},
            {"4,567", "four thousand five hundred sixty-seven"},
            {"15,943", "fifteen thousand nine hundred forty-three"},
            {
                "2,345,678",
                "two million three hundred forty-five " + "thousand six hundred seventy-eight"
            },
            {"-36", "minus thirty-six"},
            {"234.567", "two hundred thirty-four point five six seven"}
        };

        doTest(formatter, testData, true);

        String[][] testDataLenient = {
            {"fifty-7", "57"},
            {" fifty-7", "57"},
            {"  fifty-7", "57"},
            {"2 thousand six    HUNDRED fifty-7", "2,657"},
            {"fifteen hundred and zero", "1,500"},
            {"FOurhundred     thiRTY six", "436"}
        };

        doParsingTest(formatter, testDataLenient, true);
    }

    /** Perform a simple spot check on the English ordinal-abbreviation rules */
    @Test
    public void TestOrdinalAbbreviations() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(Locale.US, RuleBasedNumberFormat.ORDINAL);
        String[][] testData = {
            {"1", "1st"},
            {"2", "2nd"},
            {"3", "3rd"},
            {"4", "4th"},
            {"7", "7th"},
            {"10", "10th"},
            {"11", "11th"},
            {"13", "13th"},
            {"20", "20th"},
            {"21", "21st"},
            {"22", "22nd"},
            {"23", "23rd"},
            {"24", "24th"},
            {"33", "33rd"},
            {"102", "102nd"},
            {"312", "312th"},
            {"12,345", "12,345th"}
        };

        doTest(formatter, testData, false);
    }

    /** Perform a simple spot check on the duration-formatting rules */
    @Test
    public void TestDurations() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(Locale.US, RuleBasedNumberFormat.DURATION);
        String[][] testData = {
            {"3,600", "1:00:00"}, // move me and I fail
            {"0", "0 sec."},
            {"1", "1 sec."},
            {"24", "24 sec."},
            {"60", "1:00"},
            {"73", "1:13"},
            {"145", "2:25"},
            {"666", "11:06"},
            //            { "3,600", "1:00:00" },
            {"3,740", "1:02:20"},
            {"10,293", "2:51:33"}
        };

        doTest(formatter, testData, true);

        String[][] fractionalTestData = {
            {"1234", "20:34"},
            {"1234.2", "20:34"},
            {"1234.7", "20:35"}
        };
        doTest(formatter, fractionalTestData, false);

        String[][] testDataLenient = {
            {"2-51-33", "10,293"},
        };

        doParsingTest(formatter, testDataLenient, true);
    }

    @Test
    public void TestDFRounding() {
        // test for ICU-22611
        RuleBasedNumberFormat nf;

        // no decimal places
        nf = new RuleBasedNumberFormat("1000/1000: <##K<;", Locale.US);
        assertEquals("-1K", nf.format(-1400));
        assertEquals("-2K", nf.format(-1900));
        assertEquals("1K", nf.format(1400));
        assertEquals("2K", nf.format(1900));

        // 1 decimal place
        nf = new RuleBasedNumberFormat("1000/1000: <##.0K<;", Locale.US);
        assertEquals("-1.4K", nf.format(-1440));
        assertEquals("1.9K", nf.format(1890));

        // with modulus substitution
        nf = new RuleBasedNumberFormat("1000/1000: <##<K>##>; -x: ->>;", Locale.US);
        assertEquals("-1K400", nf.format(-1400));
        assertEquals("-1K900", nf.format(-1900));
        assertEquals("1K400", nf.format(1400));
        assertEquals("1K900", nf.format(1900));

        // no decimal places, but with rounding mode set to ROUND_FLOOR
        nf = new RuleBasedNumberFormat("1000/1000: <##K<;", Locale.US);
        nf.setMaximumFractionDigits(0);
        nf.setRoundingMode(BigDecimal.ROUND_FLOOR);
        assertEquals("-2K", nf.format(-1400));
        assertEquals("-2K", nf.format(-1900));
        assertEquals("1K", nf.format(1400));
        assertEquals("1K", nf.format(1900));
    }

    /** Perform a simple spot check on the Spanish spellout rules */
    @Test
    public void TestSpanishSpellout() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(
                        new Locale("es", "es", ""), RuleBasedNumberFormat.SPELLOUT);
        String[][] testData = {
            {"1", "uno"},
            {"6", "seis"},
            {"16", "diecis\u00e9is"},
            {"20", "veinte"},
            {"24", "veinticuatro"},
            {"26", "veintis\u00e9is"},
            {"73", "setenta y tres"},
            {"88", "ochenta y ocho"},
            {"100", "cien"},
            {"106", "ciento seis"},
            {"127", "ciento veintisiete"},
            {"200", "doscientos"},
            {"579", "quinientos setenta y nueve"},
            {"1,000", "mil"},
            {"2,000", "dos mil"},
            {"3,004", "tres mil cuatro"},
            {"4,567", "cuatro mil quinientos sesenta y siete"},
            {"15,943", "quince mil novecientos cuarenta y tres"},
            {
                "2,345,678",
                "dos millones trescientos cuarenta y cinco mil " + "seiscientos setenta y ocho"
            },
            {"-36", "menos treinta y seis"},
            {"234.567", "doscientos treinta y cuatro coma cinco seis siete"}
        };

        doTest(formatter, testData, true);
    }

    /** Perform a simple spot check on the French spellout rules */
    @Test
    public void TestFrenchSpellout() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(Locale.FRANCE, RuleBasedNumberFormat.SPELLOUT);
        String[][] testData = {
            {"1", "un"},
            {"15", "quinze"},
            {"20", "vingt"},
            {"21", "vingt-et-un"},
            {"23", "vingt-trois"},
            {"62", "soixante-deux"},
            {"70", "soixante-dix"},
            {"71", "soixante-et-onze"},
            {"73", "soixante-treize"},
            {"80", "quatre-vingts"},
            {"88", "quatre-vingt-huit"},
            {"100", "cent"},
            {"106", "cent six"},
            {"127", "cent vingt-sept"},
            {"200", "deux cents"},
            {"579", "cinq cent soixante-dix-neuf"},
            {"1,000", "mille"},
            {"1,123", "mille cent vingt-trois"},
            {"1,594", "mille cinq cent quatre-vingt-quatorze"},
            {"2,000", "deux mille"},
            {"3,004", "trois mille quatre"},
            {"4,567", "quatre mille cinq cent soixante-sept"},
            {"15,943", "quinze mille neuf cent quarante-trois"},
            {
                "2,345,678",
                "deux millions trois cent quarante-cinq mille " + "six cent soixante-dix-huit"
            },
            {"-36", "moins trente-six"},
            {"234.567", "deux cent trente-quatre virgule cinq six sept"}
        };

        doTest(formatter, testData, true);

        String[][] testDataLenient = {
            {"trente-et-un", "31"},
            {"un cent quatre vingt dix huit", "198"},
        };

        doParsingTest(formatter, testDataLenient, true);
    }

    /** Perform a simple spot check on the Swiss French spellout rules */
    @Test
    public void TestSwissFrenchSpellout() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(new Locale("fr", "CH"), RuleBasedNumberFormat.SPELLOUT);
        String[][] testData = {
            {"1", "un"},
            {"15", "quinze"},
            {"20", "vingt"},
            {"21", "vingt-et-un"},
            {"23", "vingt-trois"},
            {"62", "soixante-deux"},
            {"70", "septante"},
            {"71", "septante-et-un"},
            {"73", "septante-trois"},
            {"80", "huitante"},
            {"88", "huitante-huit"},
            {"100", "cent"},
            {"106", "cent six"},
            {"127", "cent vingt-sept"},
            {"200", "deux cents"},
            {"579", "cinq cent septante-neuf"},
            {"1,000", "mille"},
            {"1,123", "mille cent vingt-trois"},
            {"1,594", "mille cinq cent nonante-quatre"},
            {"2,000", "deux mille"},
            {"3,004", "trois mille quatre"},
            {"4,567", "quatre mille cinq cent soixante-sept"},
            {"15,943", "quinze mille neuf cent quarante-trois"},
            {
                "2,345,678",
                "deux millions trois cent quarante-cinq mille " + "six cent septante-huit"
            },
            {"-36", "moins trente-six"},
            {"234.567", "deux cent trente-quatre virgule cinq six sept"}
        };

        doTest(formatter, testData, true);
    }

    /** Perform a simple spot check on the Italian spellout rules */
    @Test
    public void TestItalianSpellout() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(Locale.ITALIAN, RuleBasedNumberFormat.SPELLOUT);
        String[][] testData = {
            {"1", "uno"},
            {"15", "quindici"},
            {"20", "venti"},
            {"23", "venti\u00ADtr\u00E9"},
            {"73", "settanta\u00ADtr\u00E9"},
            {"88", "ottant\u00ADotto"},
            {"100", "cento"},
            {"106", "cento\u00ADsei"},
            {"108", "cent\u00ADotto"},
            {"127", "cento\u00ADventi\u00ADsette"},
            {"181", "cent\u00ADottant\u00ADuno"},
            {"200", "due\u00ADcento"},
            {"579", "cinque\u00ADcento\u00ADsettanta\u00ADnove"},
            {"1,000", "mille"},
            {"2,000", "due\u00ADmila"},
            {"3,004", "tre\u00ADmila\u00ADquattro"},
            {"4,567", "quattro\u00ADmila\u00ADcinque\u00ADcento\u00ADsessanta\u00ADsette"},
            {"15,943", "quindici\u00ADmila\u00ADnove\u00ADcento\u00ADquaranta\u00ADtr\u00E9"},
            {"-36", "meno trenta\u00ADsei"},
            {"234.567", "due\u00ADcento\u00ADtrenta\u00ADquattro virgola cinque sei sette"}
        };

        doTest(formatter, testData, true);
    }

    /** Perform a simple spot check on the German spellout rules */
    @Test
    public void TestGermanSpellout() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(Locale.GERMANY, RuleBasedNumberFormat.SPELLOUT);
        String[][] testData = {
            {"1", "eins"},
            {"15", "f\u00fcnfzehn"},
            {"20", "zwanzig"},
            {"23", "drei\u00ADund\u00ADzwanzig"},
            {"73", "drei\u00ADund\u00ADsiebzig"},
            {"88", "acht\u00ADund\u00ADachtzig"},
            {"100", "ein\u00ADhundert"},
            {"106", "ein\u00ADhundert\u00ADsechs"},
            {"127", "ein\u00ADhundert\u00ADsieben\u00ADund\u00ADzwanzig"},
            {"200", "zwei\u00ADhundert"},
            {"579", "f\u00fcnf\u00ADhundert\u00ADneun\u00ADund\u00ADsiebzig"},
            {"1,000", "ein\u00ADtausend"},
            {"1,101", "ein\u00adtausend\u00adein\u00adhundert\u00adeins"},
            {"2,000", "zwei\u00ADtausend"},
            {"3,004", "drei\u00ADtausend\u00ADvier"},
            {
                "4,567",
                "vier\u00ADtausend\u00ADf\u00fcnf\u00ADhundert\u00ADsieben\u00ADund\u00ADsechzig"
            },
            {
                "15,943",
                "f\u00fcnfzehn\u00ADtausend\u00ADneun\u00ADhundert\u00ADdrei\u00ADund\u00ADvierzig"
            },
            {
                "2,345,678",
                "zwei Millionen drei\u00ADhundert\u00ADf\u00fcnf\u00ADund\u00ADvierzig\u00ADtausend\u00AD"
                        + "sechs\u00ADhundert\u00ADacht\u00ADund\u00ADsiebzig"
            }
        };

        doTest(formatter, testData, true);

        String[][] testDataLenient = {
            {"ein Tausend sechs Hundert fuenfunddreissig", "1,635"},
        };

        doParsingTest(formatter, testDataLenient, true);

        String[][] testDataYear = {
            {"101", "ein\u00adhundert\u00adeins"},
            {"900", "neun\u00adhundert"},
            {"1,001", "ein\u00adtausend\u00adeins"},
            {"1,100", "elf\u00adhundert"},
            {"1,101", "elf\u00adhundert\u00adeins"},
            {"1,234", "zw\u00f6lf\u00adhundert\u00advier\u00adund\u00addrei\u00dfig"},
            {"2,001", "zwei\u00adtausend\u00adeins"},
            {"10,001", "zehn\u00adtausend\u00adeins"},
            {"-100", "minus ein\u00adhundert"},
            {"12.34", "12,3"},
        };

        formatter.setDefaultRuleSet("%spellout-numbering-year");
        logln("testing year rules");
        doTest(formatter, testDataYear, false);
    }

    /** Perform a simple spot check on the Thai spellout rules */
    @Test
    public void TestThaiSpellout() {
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(new Locale("th", "TH"), RuleBasedNumberFormat.SPELLOUT);
        String[][] testData = {
            {"0", "\u0e28\u0e39\u0e19\u0e22\u0e4c"},
            {"1", "\u0e2b\u0e19\u0e36\u0e48\u0e07"},
            {"10", "\u0e2a\u0e34\u0e1a"},
            {"11", "\u0e2a\u0e34\u0e1a\u200b\u0e40\u0e2d\u0e47\u0e14"},
            {"21", "\u0e22\u0e35\u0e48\u200b\u0e2a\u0e34\u0e1a\u200b\u0e40\u0e2d\u0e47\u0e14"},
            {
                "101",
                "\u0e2b\u0e19\u0e36\u0e48\u0e07\u200b\u0e23\u0e49\u0e2d\u0e22\u200b\u0e2b\u0e19\u0e36\u0e48\u0e07"
            },
            {
                "1.234",
                "\u0e2b\u0e19\u0e36\u0e48\u0e07\u200b\u0e08\u0e38\u0e14\u200b\u0e2a\u0e2d\u0e07\u0e2a\u0e32\u0e21\u0e2a\u0e35\u0e48"
            },
            {
                "21.45",
                "\u0e22\u0e35\u0e48\u200b\u0e2a\u0e34\u0e1a\u200b\u0e40\u0e2d\u0e47\u0e14\u200b\u0e08\u0e38\u0e14\u200b\u0e2a\u0e35\u0e48\u0e2b\u0e49\u0e32"
            },
            {
                "22.45",
                "\u0e22\u0e35\u0e48\u200b\u0e2a\u0e34\u0e1a\u200b\u0e2a\u0e2d\u0e07\u200b\u0e08\u0e38\u0e14\u200b\u0e2a\u0e35\u0e48\u0e2b\u0e49\u0e32"
            },
            {
                "23.45",
                "\u0e22\u0e35\u0e48\u200b\u0e2a\u0e34\u0e1a\u200b\u0e2a\u0e32\u0e21\u200b\u0e08\u0e38\u0e14\u200b\u0e2a\u0e35\u0e48\u0e2b\u0e49\u0e32"
            },
            {
                "123.45",
                "\u0e2b\u0e19\u0e36\u0e48\u0e07\u200b\u0e23\u0e49\u0e2d\u0e22\u200b\u0e22\u0e35\u0e48\u200b\u0e2a\u0e34\u0e1a\u200b\u0e2a\u0e32\u0e21\u200b\u0e08\u0e38\u0e14\u200b\u0e2a\u0e35\u0e48\u0e2b\u0e49\u0e32"
            },
            {
                "12,345.678",
                "\u0E2B\u0E19\u0E36\u0E48\u0E07\u200b\u0E2B\u0E21\u0E37\u0E48\u0E19\u200b\u0E2A\u0E2D\u0E07\u200b\u0E1E\u0E31\u0E19\u200b\u0E2A\u0E32\u0E21\u200b\u0E23\u0E49\u0E2D\u0E22\u200b\u0E2A\u0E35\u0E48\u200b\u0E2A\u0E34\u0E1A\u200b\u0E2B\u0E49\u0E32\u200b\u0E08\u0E38\u0E14\u200b\u0E2B\u0E01\u0E40\u0E08\u0E47\u0E14\u0E41\u0E1B\u0E14"
            },
        };

        doTest(formatter, testData, true);
    }

    /** Perform a simple spot check on the ordinal spellout rules */
    @Test
    public void TestPluralRules() {
        String enRules =
                "%digits-ordinal:"
                        + "-x: −>>;"
                        + "0: =#,##0=$(ordinal,one{st}two{nd}few{rd}other{th})$;";
        RuleBasedNumberFormat enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
        String[][] enTestData = {
            {"1", "1st"},
            {"2", "2nd"},
            {"3", "3rd"},
            {"4", "4th"},
            {"11", "11th"},
            {"12", "12th"},
            {"13", "13th"},
            {"14", "14th"},
            {"21", "21st"},
            {"22", "22nd"},
            {"23", "23rd"},
            {"24", "24th"},
        };

        doTest(enFormatter, enTestData, true);

        // This is trying to model the feminine form, but don't worry about the details too much.
        // We're trying to test the plural rules.
        String ruRules =
                "%spellout-numbering:"
                        + "-x: минус >>;"
                        + "x.x: [<< $(cardinal,one{целый}other{целых})$ ]>%%fractions-feminine>;"
                        + "0: ноль;"
                        + "1: один;"
                        + "2: два;"
                        + "3: три;"
                        + "4: четыре;"
                        + "5: пять;"
                        + "6: шесть;"
                        + "7: семь;"
                        + "8: восемь;"
                        + "9: девять;"
                        + "10: десять;"
                        + "11: одиннадцать;"
                        + "12: двенадцать;"
                        + "13: тринадцать;"
                        + "14: четырнадцать;"
                        + "15: пятнадцать;"
                        + "16: шестнадцать;"
                        + "17: семнадцать;"
                        + "18: восемнадцать;"
                        + "19: девятнадцать;"
                        + "20: двадцать[ >>];"
                        + "30: тридцать[ >>];"
                        + "40: сорок[ >>];"
                        + "50: пятьдесят[ >>];"
                        + "60: шестьдесят[ >>];"
                        + "70: семьдесят[ >>];"
                        + "80: восемьдесят[ >>];"
                        + "90: девяносто[ >>];"
                        + "100: сто[ >>];"
                        + "200: <<сти[ >>];"
                        + "300: <<ста[ >>];"
                        + "500: <<сот[ >>];"
                        + "1000: << $(cardinal,one{тысяча}few{тысячи}other{тысяч})$[ >>];"
                        + "1000000: << $(cardinal,one{миллион}few{миллионы}other{миллионов})$[ >>];"
                        + "%%fractions-feminine:"
                        + "10: <%spellout-numbering< $(cardinal,one{десятая}other{десятых})$;"
                        + "100: <%spellout-numbering< $(cardinal,one{сотая}other{сотых})$;";
        RuleBasedNumberFormat ruFormatter = new RuleBasedNumberFormat(ruRules, new ULocale("ru"));
        String[][] ruTestData = {
            {"1", "один"},
            {"100", "сто"},
            {"125", "сто двадцать пять"},
            {"399", "триста девяносто девять"},
            {"1,000", "один тысяча"},
            {"1,001", "один тысяча один"},
            {"2,000", "два тысячи"},
            {"2,001", "два тысячи один"},
            {"2,002", "два тысячи два"},
            {"3,333", "три тысячи триста тридцать три"},
            {"5,000", "пять тысяч"},
            {"11,000", "одиннадцать тысяч"},
            {"21,000", "двадцать один тысяча"},
            {"22,000", "двадцать два тысячи"},
            {"25,001", "двадцать пять тысяч один"},
            {"0.1", "один десятая"},
            {"0.2", "два десятых"},
            {"0.21", "двадцать один сотая"},
            {"0.22", "двадцать два сотых"},
            {"21.1", "двадцать один целый один десятая"},
            {"22.2", "двадцать два целых два десятых"},
        };

        doTest(ruFormatter, ruTestData, true);

        // Make sure there are no divide by 0 errors.
        String result = new RuleBasedNumberFormat(ruRules, new ULocale("ru")).format(21000);
        if (!"двадцать один тысяча".equals(result)) {
            errln("Got " + result + " for 21000");
        }
    }

    /**
     * Perform a simple spot check on the parsing going into an infinite loop for alternate rules.
     */
    @Test
    public void TestMultiplePluralRules() {
        // This is trying to model the feminine form, but don't worry about the details too much.
        // We're trying to test the plural rules where there are different prefixes.
        String ruRules =
                "%spellout-cardinal-feminine-genitive:"
                        + "-x: минус >>;"
                        + "x.x: << запятая >>;"
                        + "0: ноля;"
                        + "1: одной;"
                        + "2: двух;"
                        + "3: трех;"
                        + "4: четырех;"
                        + "5: пяти;"
                        + "6: шести;"
                        + "7: семи;"
                        + "8: восьми;"
                        + "9: девяти;"
                        + "10: десяти;"
                        + "11: одиннадцати;"
                        + "12: двенадцати;"
                        + "13: тринадцати;"
                        + "14: четырнадцати;"
                        + "15: пятнадцати;"
                        + "16: шестнадцати;"
                        + "17: семнадцати;"
                        + "18: восемнадцати;"
                        + "19: девятнадцати;"
                        + "20: двадцати[ >>];"
                        + "30: тридцати[ >>];"
                        + "40: сорока[ >>];"
                        + "50: пятидесяти[ >>];"
                        + "60: шестидесяти[ >>];"
                        + "70: семидесяти[ >>];"
                        + "80: восемидесяти[ >>];"
                        + "90: девяноста[ >>];"
                        + "100: ста[ >>];"
                        + "200: <<сот[ >>];"
                        + "1000: << $(cardinal,one{тысяча}few{тысячи}other{тысяч})$[ >>];"
                        + "1000000: =#,##0=;"
                        + "%spellout-cardinal-feminine:"
                        + "-x: минус >>;"
                        + "x.x: << запятая >>;"
                        + "0: ноль;"
                        + "1: одна;"
                        + "2: две;"
                        + "3: три;"
                        + "4: четыре;"
                        + "5: пять;"
                        + "6: шесть;"
                        + "7: семь;"
                        + "8: восемь;"
                        + "9: девять;"
                        + "10: десять;"
                        + "11: одиннадцать;"
                        + "12: двенадцать;"
                        + "13: тринадцать;"
                        + "14: четырнадцать;"
                        + "15: пятнадцать;"
                        + "16: шестнадцать;"
                        + "17: семнадцать;"
                        + "18: восемнадцать;"
                        + "19: девятнадцать;"
                        + "20: двадцать[ >>];"
                        + "30: тридцать[ >>];"
                        + "40: сорок[ >>];"
                        + "50: пятьдесят[ >>];"
                        + "60: шестьдесят[ >>];"
                        + "70: семьдесят[ >>];"
                        + "80: восемьдесят[ >>];"
                        + "90: девяносто[ >>];"
                        + "100: сто[ >>];"
                        + "200: <<сти[ >>];"
                        + "300: <<ста[ >>];"
                        + "500: <<сот[ >>];"
                        + "1000: << $(cardinal,one{тысяча}few{тысячи}other{тысяч})$[ >>];"
                        + "1000000: =#,##0=;";
        RuleBasedNumberFormat ruFormatter = new RuleBasedNumberFormat(ruRules, new ULocale("ru"));
        try {
            Number result;
            if (1000 != (result = ruFormatter.parse(ruFormatter.format(1000))).doubleValue()) {
                errln("RuleBasedNumberFormat did not return the correct value. Got: " + result);
            }
            if (1000
                    != (result =
                                    ruFormatter.parse(
                                            ruFormatter.format(
                                                    1000, "%spellout-cardinal-feminine-genitive")))
                            .doubleValue()) {
                errln("RuleBasedNumberFormat did not return the correct value. Got: " + result);
            }
            if (1000
                    != (result =
                                    ruFormatter.parse(
                                            ruFormatter.format(
                                                    1000, "%spellout-cardinal-feminine")))
                            .doubleValue()) {
                errln("RuleBasedNumberFormat did not return the correct value. Got: " + result);
            }
        } catch (ParseException e) {
            errln(e.toString());
        }
    }

    @Test
    public void TestFractionalRuleSet() {
        RuleBasedNumberFormat formatter = new RuleBasedNumberFormat(fracRules, Locale.ENGLISH);

        String[][] testData = {
            {"0", "0"},
            {"1", "1"},
            {"10", "10"},
            {".1", "1/10"},
            {".11", "1/9"},
            {".125", "1/8"},
            {".1428", "1/7"},
            {".1667", "1/6"},
            {".2", "1/5"},
            {".25", "1/4"},
            {".333", "1/3"},
            {".5", "1/2"},
            {"1.1", "1 1/10"},
            {"2.11", "2 1/9"},
            {"3.125", "3 1/8"},
            {"4.1428", "4 1/7"},
            {"5.1667", "5 1/6"},
            {"6.2", "6 1/5"},
            {"7.25", "7 1/4"},
            {"8.333", "8 1/3"},
            {"9.5", "9 1/2"},
            {".2222", "2/9"},
            {".4444", "4/9"},
            {".5555", "5/9"},
            {"1.2856", "1 2/7"}
        };
        doTest(formatter, testData, false); // exact values aren't parsable from fractions
    }

    @Test
    public void TestNorwegianSpellout() {
        Locale noLocale = new Locale("no", "", "");
        Locale nbLocale = new Locale("nb", "", "");
        RuleBasedNumberFormat noFormatter =
                new RuleBasedNumberFormat(noLocale, RuleBasedNumberFormat.SPELLOUT);
        RuleBasedNumberFormat nbFormatter =
                new RuleBasedNumberFormat(nbLocale, RuleBasedNumberFormat.SPELLOUT);

        String[][] testDataDefault = {
            {"1", "\u00E9n"},
            {"2", "to"},
            {"3", "tre"},
            {"4", "fire"},
            {"101", "hundre og \u00E9n"},
            {"123", "hundre og tjue\u00ADtre"},
            {"1,001", "tusen og \u00E9n"},
            {"1,100", "tusen hundre"},
            {"6.789", "seks komma sju \u00E5tte ni"},
            {"-5.678", "minus fem komma seks sju \u00E5tte"},
        };

        logln("testing default rules");
        doTest(noFormatter, testDataDefault, true);
        doTest(nbFormatter, testDataDefault, true);
    }

    @Test
    public void TestSwedishSpellout() {
        Locale locale = new Locale("sv", "", "");
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(locale, RuleBasedNumberFormat.SPELLOUT);

        String[][] testDataDefault = {
            {"101", "ett\u00ADhundra\u00ADett"},
            {"123", "ett\u00ADhundra\u00ADtjugo\u00ADtre"},
            {"1,001", "et\u00ADtusen ett"},
            {"1,100", "et\u00ADtusen ett\u00ADhundra"},
            {"1,101", "et\u00ADtusen ett\u00ADhundra\u00ADett"},
            {"1,234", "et\u00ADtusen tv\u00e5\u00ADhundra\u00ADtrettio\u00ADfyra"},
            {"10,001", "tio\u00ADtusen ett"},
            {"11,000", "elva\u00ADtusen"},
            {"12,000", "tolv\u00ADtusen"},
            {"20,000", "tjugo\u00ADtusen"},
            {"21,000", "tjugo\u00ADet\u00ADtusen"},
            {"21,001", "tjugo\u00ADet\u00ADtusen ett"},
            {"200,000", "tv\u00e5\u00ADhundra\u00ADtusen"},
            {"201,000", "tv\u00e5\u00ADhundra\u00ADet\u00ADtusen"},
            {"200,200", "tv\u00e5\u00ADhundra\u00ADtusen tv\u00e5\u00ADhundra"},
            {"2,002,000", "tv\u00e5 miljoner tv\u00e5\u00ADtusen"},
            {
                "12,345,678",
                "tolv miljoner tre\u00ADhundra\u00ADfyrtio\u00ADfem\u00ADtusen sex\u00ADhundra\u00ADsjuttio\u00AD\u00e5tta"
            },
            {
                "123,456.789",
                "ett\u00ADhundra\u00ADtjugo\u00ADtre\u00ADtusen fyra\u00ADhundra\u00ADfemtio\u00ADsex komma sju \u00e5tta nio"
            },
            {
                "-12,345.678",
                "minus tolv\u00ADtusen tre\u00ADhundra\u00ADfyrtio\u00ADfem komma sex sju \u00e5tta"
            },
        };

        logln("testing default rules");
        doTest(formatter, testDataDefault, true);

        String[][] testDataNeutrum = {
            {"101", "ett\u00adhundra\u00adett"},
            {"1,001", "et\u00adtusen ett"},
            {"1,101", "et\u00adtusen ett\u00adhundra\u00adett"},
            {"10,001", "tio\u00adtusen ett"},
            {"21,001", "tjugo\u00adet\u00adtusen ett"}
        };

        formatter.setDefaultRuleSet("%spellout-cardinal-neuter");
        logln("testing neutrum rules");
        doTest(formatter, testDataNeutrum, true);

        String[][] testDataYear = {
            {"101", "ett\u00adhundra\u00adett"},
            {"900", "nio\u00adhundra"},
            {"1,001", "et\u00adtusen ett"},
            {"1,100", "elva\u00adhundra"},
            {"1,101", "elva\u00adhundra\u00adett"},
            {"1,234", "tolv\u00adhundra\u00adtrettio\u00adfyra"},
            {"2,001", "tjugo\u00adhundra\u00adett"},
            {"10,001", "tio\u00adtusen ett"}
        };

        formatter.setDefaultRuleSet("%spellout-numbering-year");
        logln("testing year rules");
        doTest(formatter, testDataYear, true);
    }

    @Test
    public void TestJpanyear() {
        Locale locale = new Locale("ja");
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(locale, RuleBasedNumberFormat.SPELLOUT);

        String[][] testDataYear = {
            {"0", "0"},
            {"1", "\u5143"}, // 元
            {"2", "2"},
            {"10", "10"},
            {"31", "31"},
        };

        formatter.setDefaultRuleSet("%spellout-numbering-year-latn");
        logln("testing year rules");
        doTest(formatter, testDataYear, true);
    }

    @Test
    public void TestBigNumbers() {
        BigInteger bigI = new BigInteger("1234567890", 10);
        StringBuffer buf = new StringBuffer();
        RuleBasedNumberFormat fmt = new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
        fmt.format(bigI, buf, null);
        logln("big int: " + buf.toString());

        buf.setLength(0);
        java.math.BigDecimal bigD = new java.math.BigDecimal(bigI);
        fmt.format(bigD, buf, null);
        logln("big dec: " + buf.toString());
    }

    @Test
    public void TestTrailingSemicolon() {
        String thaiRules =
                "%default:\n"
                        + "  -x: \u0e25\u0e1a>>;\n"
                        + "  x.x: <<\u0e08\u0e38\u0e14>>>;\n"
                        + "  \u0e28\u0e39\u0e19\u0e22\u0e4c; \u0e2b\u0e19\u0e36\u0e48\u0e07; \u0e2a\u0e2d\u0e07; \u0e2a\u0e32\u0e21;\n"
                        + "  \u0e2a\u0e35\u0e48; \u0e2b\u0e49\u0e32; \u0e2b\u0e01; \u0e40\u0e08\u0e47\u0e14; \u0e41\u0e1b\u0e14;\n"
                        + "  \u0e40\u0e01\u0e49\u0e32; \u0e2a\u0e34\u0e1a; \u0e2a\u0e34\u0e1a\u0e40\u0e2d\u0e47\u0e14;\n"
                        + "  \u0e2a\u0e34\u0e1a\u0e2a\u0e2d\u0e07; \u0e2a\u0e34\u0e1a\u0e2a\u0e32\u0e21;\n"
                        + "  \u0e2a\u0e34\u0e1a\u0e2a\u0e35\u0e48; \u0e2a\u0e34\u0e1a\u0e2b\u0e49\u0e32;\n"
                        + "  \u0e2a\u0e34\u0e1a\u0e2b\u0e01; \u0e2a\u0e34\u0e1a\u0e40\u0e08\u0e47\u0e14;\n"
                        + "  \u0e2a\u0e34\u0e1a\u0e41\u0e1b\u0e14; \u0e2a\u0e34\u0e1a\u0e40\u0e01\u0e49\u0e32;\n"
                        + "  20: \u0e22\u0e35\u0e48\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  30: \u0e2a\u0e32\u0e21\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  40: \u0e2a\u0e35\u0e48\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  50: \u0e2b\u0e49\u0e32\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  60: \u0e2b\u0e01\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  70: \u0e40\u0e08\u0e47\u0e14\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  80: \u0e41\u0e1b\u0e14\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  90: \u0e40\u0e01\u0e49\u0e32\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n"
                        + "  100: <<\u0e23\u0e49\u0e2d\u0e22[>>];\n"
                        + "  1000: <<\u0e1e\u0e31\u0e19[>>];\n"
                        + "  10000: <<\u0e2b\u0e21\u0e37\u0e48\u0e19[>>];\n"
                        + "  100000: <<\u0e41\u0e2a\u0e19[>>];\n"
                        + "  1,000,000: <<\u0e25\u0e49\u0e32\u0e19[>>];\n"
                        + "  1,000,000,000: <<\u0e1e\u0e31\u0e19\u0e25\u0e49\u0e32\u0e19[>>];\n"
                        + "  1,000,000,000,000: <<\u0e25\u0e49\u0e32\u0e19\u0e25\u0e49\u0e32\u0e19[>>];\n"
                        + "  1,000,000,000,000,000: =#,##0=;\n"
                        + "%%alt-ones:\n"
                        + "  \u0e28\u0e39\u0e19\u0e22\u0e4c;\n"
                        + "  \u0e40\u0e2d\u0e47\u0e14;\n"
                        + "  =%default=;\n ; ;; ";

        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(thaiRules, new Locale("th", "TH", ""));

        String[][] testData = {
            {"0", "\u0e28\u0e39\u0e19\u0e22\u0e4c"},
            {"1", "\u0e2b\u0e19\u0e36\u0e48\u0e07"},
            {
                "123.45",
                "\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e23\u0e49\u0e2d\u0e22\u0e22\u0e35\u0e48\u0e2a\u0e34\u0e1a\u0e2a\u0e32\u0e21\u0e08\u0e38\u0e14\u0e2a\u0e35\u0e48\u0e2b\u0e49\u0e32"
            }
        };

        doTest(formatter, testData, true);
    }

    @Test
    public void TestSmallValues() {
        String[][] testData = {
            {"0.001", "zero point zero zero one"},
            {"0.0001", "zero point zero zero zero one"},
            {"0.00001", "zero point zero zero zero zero one"},
            {"0.000001", "zero point zero zero zero zero zero one"},
            {"0.0000001", "zero point zero zero zero zero zero zero one"},
            {"0.00000001", "zero point zero zero zero zero zero zero zero one"},
            {"0.000000001", "zero point zero zero zero zero zero zero zero zero one"},
            {"0.0000000001", "zero point zero zero zero zero zero zero zero zero zero one"},
            {"0.00000000001", "zero point zero zero zero zero zero zero zero zero zero zero one"},
            {
                "0.000000000001",
                "zero point zero zero zero zero zero zero zero zero zero zero zero one"
            },
            {
                "0.0000000000001",
                "zero point zero zero zero zero zero zero zero zero zero zero zero zero one"
            },
            {
                "0.00000000000001",
                "zero point zero zero zero zero zero zero zero zero zero zero zero zero zero one"
            },
            {
                "0.000000000000001",
                "zero point zero zero zero zero zero zero zero zero zero zero zero zero zero zero one"
            },
            {"10,000,000.001", "ten million point zero zero one"},
            {"10,000,000.0001", "ten million point zero zero zero one"},
            {"10,000,000.00001", "ten million point zero zero zero zero one"},
            {"10,000,000.000001", "ten million point zero zero zero zero zero one"},
            {"10,000,000.0000001", "ten million point zero zero zero zero zero zero one"},
            {"10,000,000.00000001", "ten million point zero zero zero zero zero zero zero one"},
            {
                "10,000,000.000000002",
                "ten million point zero zero zero zero zero zero zero zero two"
            },
            {"10,000,000", "ten million"},
            {
                "1,234,567,890.0987654",
                "one billion two hundred thirty-four million five hundred sixty-seven thousand eight hundred ninety point zero nine eight seven six five four"
            },
            {
                "123,456,789.9876543",
                "one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine point nine eight seven six five four three"
            },
            {
                "12,345,678.87654321",
                "twelve million three hundred forty-five thousand six hundred seventy-eight point eight seven six five four three two one"
            },
            {
                "1,234,567.7654321",
                "one million two hundred thirty-four thousand five hundred sixty-seven point seven six five four three two one"
            },
            {
                "123,456.654321",
                "one hundred twenty-three thousand four hundred fifty-six point six five four three two one"
            },
            {
                "12,345.54321",
                "twelve thousand three hundred forty-five point five four three two one"
            },
            {"1,234.4321", "one thousand two hundred thirty-four point four three two one"},
            {"123.321", "one hundred twenty-three point three two one"},
            {
                "0.0000000011754944",
                "zero point zero zero zero zero zero zero zero zero one one seven five four nine four four"
            },
            {
                "0.000001175494351",
                "zero point zero zero zero zero zero one one seven five four nine four three five one"
            },
        };

        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(Locale.US, RuleBasedNumberFormat.SPELLOUT);
        doTest(formatter, testData, true);
    }

    @Test
    public void TestRuleSetDisplayName() {
        /*
         * Spellout rules for U.K. English.
         * This was borrowed from the rule sets for TestRuleSetDisplayName()
         */
        final String ukEnglish =
                "%simplified:\n"
                        + "    -x: minus >>;\n"
                        + "    x.x: << point >>;\n"
                        + "    zero; one; two; three; four; five; six; seven; eight; nine;\n"
                        + "    ten; eleven; twelve; thirteen; fourteen; fifteen; sixteen;\n"
                        + "        seventeen; eighteen; nineteen;\n"
                        + "    20: twenty[->>];\n"
                        + "    30: thirty[->>];\n"
                        + "    40: forty[->>];\n"
                        + "    50: fifty[->>];\n"
                        + "    60: sixty[->>];\n"
                        + "    70: seventy[->>];\n"
                        + "    80: eighty[->>];\n"
                        + "    90: ninety[->>];\n"
                        + "    100: << hundred[ >>];\n"
                        + "    1000: << thousand[ >>];\n"
                        + "    1,000,000: << million[ >>];\n"
                        + "    1,000,000,000,000: << billion[ >>];\n"
                        + "    1,000,000,000,000,000: =#,##0=;\n"
                        + "%alt-teens:\n"
                        + "    =%simplified=;\n"
                        + "    1000>: <%%alt-hundreds<[ >>];\n"
                        + "    10,000: =%simplified=;\n"
                        + "    1,000,000: << million[ >%simplified>];\n"
                        + "    1,000,000,000,000: << billion[ >%simplified>];\n"
                        + "    1,000,000,000,000,000: =#,##0=;\n"
                        + "%%alt-hundreds:\n"
                        + "    0: SHOULD NEVER GET HERE!;\n"
                        + "    10: <%simplified< thousand;\n"
                        + "    11: =%simplified= hundred>%%empty>;\n"
                        + "%%empty:\n"
                        + "    0:;"
                        + "%ordinal:\n"
                        + "    zeroth; first; second; third; fourth; fifth; sixth; seventh;\n"
                        + "        eighth; ninth;\n"
                        + "    tenth; eleventh; twelfth; thirteenth; fourteenth;\n"
                        + "        fifteenth; sixteenth; seventeenth; eighteenth;\n"
                        + "        nineteenth;\n"
                        + "    twentieth; twenty->>;\n"
                        + "    30: thirtieth; thirty->>;\n"
                        + "    40: fortieth; forty->>;\n"
                        + "    50: fiftieth; fifty->>;\n"
                        + "    60: sixtieth; sixty->>;\n"
                        + "    70: seventieth; seventy->>;\n"
                        + "    80: eightieth; eighty->>;\n"
                        + "    90: ninetieth; ninety->>;\n"
                        + "    100: <%simplified< hundredth; <%simplified< hundred >>;\n"
                        + "    1000: <%simplified< thousandth; <%simplified< thousand >>;\n"
                        + "    1,000,000: <%simplified< millionth; <%simplified< million >>;\n"
                        + "    1,000,000,000,000: <%simplified< billionth;\n"
                        + "        <%simplified< billion >>;\n"
                        + "    1,000,000,000,000,000: =#,##0=;"
                        + "%default:\n"
                        + "    -x: minus >>;\n"
                        + "    x.x: << point >>;\n"
                        + "    =%simplified=;\n"
                        + "    100: << hundred[ >%%and>];\n"
                        + "    1000: << thousand[ >%%and>];\n"
                        + "    100,000>>: << thousand[>%%commas>];\n"
                        + "    1,000,000: << million[>%%commas>];\n"
                        + "    1,000,000,000,000: << billion[>%%commas>];\n"
                        + "    1,000,000,000,000,000: =#,##0=;\n"
                        + "%%and:\n"
                        + "    and =%default=;\n"
                        + "    100: =%default=;\n"
                        + "%%commas:\n"
                        + "    ' and =%default=;\n"
                        + "    100: , =%default=;\n"
                        + "    1000: , <%default< thousand, >%default>;\n"
                        + "    1,000,000: , =%default=;"
                        + "%%lenient-parse:\n"
                        + "    & ' ' , ',' ;\n";
        ULocale.setDefault(ULocale.US);
        String[][] localizations =
                new String[][] {
                    /* public rule sets*/
                    {"%simplified", "%default", "%ordinal"},
                    /* display names in "en_US" locale*/
                    {"en_US", "Simplified", "Default", "Ordinal"},
                    /* display names in "zh_Hans" locale*/
                    {"zh_Hans", "\u7B80\u5316", "\u7F3A\u7701", "\u5E8F\u5217"},
                    /* display names in a fake locale*/
                    {"foo_Bar_BAZ", "Simplified", "Default", "Ordinal"}
                };

        // Construct RuleBasedNumberFormat by rule sets and localizations list
        RuleBasedNumberFormat formatter =
                new RuleBasedNumberFormat(ukEnglish, localizations, ULocale.US);
        RuleBasedNumberFormat f2 = new RuleBasedNumberFormat(ukEnglish, localizations);
        assertTrue("Check the two formatters' equality", formatter.equals(f2));

        // get displayName by name
        String[] ruleSetNames = formatter.getRuleSetNames();
        for (int i = 0; i < ruleSetNames.length; i++) {
            logln("Rule set name: " + ruleSetNames[i]);
            String RSName_defLoc = formatter.getRuleSetDisplayName(ruleSetNames[i]);
            assertEquals("Display name in default locale", localizations[1][i + 1], RSName_defLoc);
            String RSName_loc = formatter.getRuleSetDisplayName(ruleSetNames[i], ULocale.CHINA);
            assertEquals("Display name in Chinese", localizations[2][i + 1], RSName_loc);
        }

        // getDefaultRuleSetName
        String defaultRS = formatter.getDefaultRuleSetName();
        // you know that the default rule set is %simplified according to rule sets string ukEnglish
        assertEquals("getDefaultRuleSetName", "%simplified", defaultRS);

        // get locales of localizations
        ULocale[] locales = formatter.getRuleSetDisplayNameLocales();
        for (int i = 0; i < locales.length; i++) {
            logln(locales[i].getName());
        }

        // get displayNames
        String[] RSNames_defLoc = formatter.getRuleSetDisplayNames();
        for (int i = 0; i < RSNames_defLoc.length; i++) {
            assertEquals(
                    "getRuleSetDisplayNames in default locale",
                    localizations[1][i + 1],
                    RSNames_defLoc[i]);
        }

        String[] RSNames_loc = formatter.getRuleSetDisplayNames(ULocale.UK);
        for (int i = 0; i < RSNames_loc.length; i++) {
            assertEquals(
                    "getRuleSetDisplayNames in English", localizations[1][i + 1], RSNames_loc[i]);
        }

        RSNames_loc = formatter.getRuleSetDisplayNames(ULocale.CHINA);
        for (int i = 0; i < RSNames_loc.length; i++) {
            assertEquals(
                    "getRuleSetDisplayNames in Chinese", localizations[2][i + 1], RSNames_loc[i]);
        }

        RSNames_loc = formatter.getRuleSetDisplayNames(new ULocale("foo_Bar_BAZ"));
        for (int i = 0; i < RSNames_loc.length; i++) {
            assertEquals(
                    "getRuleSetDisplayNames in fake locale",
                    localizations[3][i + 1],
                    RSNames_loc[i]);
        }
    }

    @Test
    public void TestAllLocales() {
        StringBuilder errors = new StringBuilder();
        String[] names = {" (spellout) ", " (ordinal) "
            // " (duration) " // English only
        };
        boolean[] lenientMode = {
            false, // non-lenient mode
            true // lenient mode
        };
        double[] numbers = {45.678, 1, 2, 10, 11, 100, 110, 200, 1000, 1111, -1111};
        int count = numbers.length;
        Random r = (count <= numbers.length ? null : createRandom());

        for (ULocale loc : NumberFormat.getAvailableULocales()) {
            for (int j = 0; j < names.length; ++j) {
                RuleBasedNumberFormat fmt = new RuleBasedNumberFormat(loc, j + 1);
                if (!loc.equals(fmt.getLocale(ULocale.ACTUAL_LOCALE))) {
                    // Skip the redundancy
                    break;
                }

                for (int c = 0; c < count; c++) {
                    double n;
                    if (c < numbers.length) {
                        n = numbers[c];
                    } else {
                        n = (r.nextInt(10000) - 3000) / 16d;
                    }

                    String s = fmt.format(n);
                    if (isVerbose()) {
                        logln(loc.getName() + names[j] + "success format: " + n + " -> " + s);
                    }

                    for (int k = 0; k < lenientMode.length; k++) {
                        try {
                            fmt.setLenientParseMode(lenientMode[k]);
                            Number num = fmt.parse(s);
                            if (isVerbose()) {
                                logln(
                                        loc.getName()
                                                + names[j]
                                                + "success parse: "
                                                + s
                                                + " -> "
                                                + num);
                            }
                            if (j != 0 && n != (int) n) {
                                // Ordinals are never fractional
                                continue;
                            }
                            if (n != num.doubleValue()) {
                                errors.append(
                                        "\n" + loc + names[j] + "got " + num + " expected " + n);
                            }
                        } catch (IllegalArgumentException | ParseException e) {
                            String msg = loc.getName() + names[j] + "ERROR:" + e.getMessage();
                            logln(msg);
                            errors.append("\n").append(msg);
                        }
                    }
                }
            }
        }
        if (errors.length() > 0) {
            errln(errors.toString());
        }
    }

    NumberFormat createDecimalFormatter() {
        return new DecimalFormat("#,###.################");
    }

    void doTest(RuleBasedNumberFormat formatter, String[][] testData, boolean testParsing) {
        NumberFormat decFmt = createDecimalFormatter();
        try {
            for (int i = 0; i < testData.length; i++) {
                String number = testData[i][0];
                String expectedWords = testData[i][1];
                if (isVerbose()) {
                    logln("test[" + i + "] number: " + number + " target: " + expectedWords);
                }
                Number num = decFmt.parse(number);
                String actualWords = formatter.format(num);

                if (!actualWords.equals(expectedWords)) {
                    errln(
                            "Spot check format failed: for "
                                    + number
                                    + ", expected\n    "
                                    + expectedWords
                                    + ", but got\n    "
                                    + actualWords);
                } else if (testParsing) {
                    String actualNumber = decFmt.format(formatter.parse(actualWords));

                    if (!actualNumber.equals(number)) {
                        errln(
                                "Spot check parse failed: for "
                                        + actualWords
                                        + ", expected "
                                        + number
                                        + ", but got "
                                        + actualNumber);
                    }
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
            errln("Test failed with exception: " + e.toString());
        }
    }

    void doParsingTest(RuleBasedNumberFormat formatter, String[][] testData, boolean lenient) {
        NumberFormat decFmt = createDecimalFormatter();

        if (lenient) {
            formatter.setLenientParseMode(true);
        }
        for (int i = 0; i < testData.length; i++) {
            try {
                String s = testData[i][0];
                Number expectedNumber = decFmt.parse(testData[i][1]);
                if (isVerbose()) {
                    logln("test[" + i + "] spellout value: (" + s + ") target: " + expectedNumber);
                }

                Number num = formatter.parse(s);
                if (isVerbose()) {
                    logln("success parse: (" + s + ") -> " + num);
                }

                if (expectedNumber.doubleValue() != num.doubleValue()) {
                    errln(
                            "\nParsing ("
                                    + s
                                    + ") failed: got "
                                    + num
                                    + " expected "
                                    + expectedNumber);
                }
            } catch (Throwable e) {
                e.printStackTrace();
                errln("Test failed with exception: " + e.toString());
            }
        }
    }

    /* Tests the method
     *      public boolean equals(Object that)
     */
    @Test
    public void TestEquals() {
        // Tests when "if (!(that instanceof RuleBasedNumberFormat))" is true
        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat("dummy");
        if (rbnf.equals("dummy")
                || rbnf.equals('a')
                || rbnf.equals(new Object())
                || rbnf.equals(-1)
                || rbnf.equals(0)
                || rbnf.equals(1)
                || rbnf.equals(-1.0)
                || rbnf.equals(0.0)
                || rbnf.equals(1.0)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was suppose to "
                            + "be false for an invalid object.");
        }

        // Tests when
        // "if (!locale.equals(that2.locale) || lenientParse != that2.lenientParse)"
        // is true
        RuleBasedNumberFormat rbnf1 = new RuleBasedNumberFormat("dummy", new Locale("en"));
        RuleBasedNumberFormat rbnf2 = new RuleBasedNumberFormat("dummy", new Locale("jp"));
        RuleBasedNumberFormat rbnf3 = new RuleBasedNumberFormat("dummy", new Locale("sp"));
        RuleBasedNumberFormat rbnf4 = new RuleBasedNumberFormat("dummy", new Locale("fr"));

        if (rbnf1.equals(rbnf2)
                || rbnf1.equals(rbnf3)
                || rbnf1.equals(rbnf4)
                || rbnf2.equals(rbnf3)
                || rbnf2.equals(rbnf4)
                || rbnf3.equals(rbnf4)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was suppose to "
                            + "be false for an invalid object.");
        }

        if (!rbnf1.equals(rbnf1)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was not suppose to "
                            + "be false for an invalid object.");
        }

        if (!rbnf2.equals(rbnf2)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was not suppose to "
                            + "be false for an invalid object.");
        }

        if (!rbnf3.equals(rbnf3)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was not suppose to "
                            + "be false for an invalid object.");
        }

        if (!rbnf4.equals(rbnf4)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was not suppose to "
                            + "be false for an invalid object.");
        }

        RuleBasedNumberFormat rbnf5 = new RuleBasedNumberFormat("dummy", new Locale("en"));
        RuleBasedNumberFormat rbnf6 = new RuleBasedNumberFormat("dummy", new Locale("en"));

        if (!rbnf5.equals(rbnf6)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was not suppose to "
                            + "be false for an invalid object.");
        }
        rbnf6.setLenientParseMode(true);

        if (rbnf5.equals(rbnf6)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was suppose to "
                            + "be false for an invalid object.");
        }

        // Tests when "if (!ruleSets[i].equals(that2.ruleSets[i]))" is true
        RuleBasedNumberFormat rbnf7 = new RuleBasedNumberFormat("not_dummy", new Locale("en"));
        if (rbnf5.equals(rbnf7)) {
            errln(
                    "RuleBasedNumberFormat.equals(Object that) was suppose to "
                            + "be false for an invalid object.");
        }
    }

    /* Tests the method
     *      public ULocale[] getRuleSetDisplayNameLocales()
     */
    @Test
    public void TestGetRuleDisplayNameLocales() {
        // Tests when "if (ruleSetDisplayNames != null" is false
        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat("dummy");
        rbnf.getRuleSetDisplayNameLocales();
        if (rbnf.getRuleSetDisplayNameLocales() != null) {
            errln(
                    "RuleBasedNumberFormat.getRuleDisplayNameLocales() was suppose to "
                            + "return null.");
        }
    }

    /* Tests the method
     *      private String[] getNameListForLocale(ULocale loc)
     *      public String[] getRuleSetDisplayNames(ULocale loc)
     */
    @Test
    public void TestGetNameListForLocale() {
        // Tests when "if (names != null)" is false and
        //  "if (loc != null && ruleSetDisplayNames != null)" is false
        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat("dummy");
        rbnf.getRuleSetDisplayNames(null);
        try {
            rbnf.getRuleSetDisplayNames(null);
        } catch (Exception e) {
            errln(
                    "RuleBasedNumberFormat.getRuleSetDisplayNames(ULocale loc) "
                            + "was not suppose to have an exception.");
        }
    }

    /* Tests the method
     *      public String getRuleSetDisplayName(String ruleSetName, ULocale loc)
     */
    @Test
    public void TestGetRulesSetDisplayName() {
        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat("dummy");
        // rbnf.getRuleSetDisplayName("dummy", new ULocale("en_US"));

        // Tests when "if (names != null) " is true

        // Tests when the method throws an exception
        try {
            rbnf.getRuleSetDisplayName("", new ULocale("en_US"));
            errln(
                    "RuleBasedNumberFormat.getRuleSetDisplayName(String ruleSetName, ULocale loc) "
                            + "was suppose to have an exception.");
        } catch (Exception ignored) {
        }

        try {
            rbnf.getRuleSetDisplayName("dummy", new ULocale("en_US"));
            errln(
                    "RuleBasedNumberFormat.getRuleSetDisplayName(String ruleSetName, ULocale loc) "
                            + "was suppose to have an exception.");
        } catch (Exception ignored) {
        }
    }

    /* Test the method
     *      public void process(StringBuffer buf, NFRuleSet ruleSet)
     */
    @Test
    public void TestChineseProcess() {
        String ruleWithChinese =
                "%simplified:\n"
                        + "    -x: minus >>;\n"
                        + "    x.x: << point >>;\n"
                        + "    zero; one; two; three; four; five; six; seven; eight; nine;\n"
                        + "    ten; eleven; twelve; thirteen; fourteen; fifteen; sixteen;\n"
                        + "        seventeen; eighteen; nineteen;\n"
                        + "    20: twenty[->>];\n"
                        + "    30: thirty[->>];\n"
                        + "    40: forty[->>];\n"
                        + "    50: fifty[->>];\n"
                        + "    60: sixty[->>];\n"
                        + "    70: seventy[->>];\n"
                        + "    80: eighty[->>];\n"
                        + "    90: ninety[->>];\n"
                        + "    100: << hundred[ >>];\n"
                        + "    1000: << thousand[ >>];\n"
                        + "    1,000,000: << million[ >>];\n"
                        + "    1,000,000,000,000: << billion[ >>];\n"
                        + "    1,000,000,000,000,000: =#,##0=;\n"
                        + "%alt-teens:\n"
                        + "    =%simplified=;\n"
                        + "    1000>: <%%alt-hundreds<[ >>];\n"
                        + "    10,000: =%simplified=;\n"
                        + "    1,000,000: << million[ >%simplified>];\n"
                        + "    1,000,000,000,000: << billion[ >%simplified>];\n"
                        + "    1,000,000,000,000,000: =#,##0=;\n"
                        + "%%alt-hundreds:\n"
                        + "    0: SHOULD NEVER GET HERE!;\n"
                        + "    10: <%simplified< thousand;\n"
                        + "    11: =%simplified= hundred>%%empty>;\n"
                        + "%%empty:\n"
                        + "    0:;"
                        + "%accounting:\n"
                        + "    \u842c; \u842c; \u842c; \u842c; \u842c; \u842c; \u842c; \u842c;\n"
                        + "        \u842c; \u842c;\n"
                        + "    \u842c; \u842c; \u842c; \u842c; \u842c;\n"
                        + "        \u842c; \u842c; \u842c; \u842c;\n"
                        + "        \u842c;\n"
                        + "    twentieth; \u96f6|>>;\n"
                        + "    30: \u96f6; \u96f6|>>;\n"
                        + "    40: \u96f6; \u96f6|>>;\n"
                        + "    50: \u96f6; \u96f6|>>;\n"
                        + "    60: \u96f6; \u96f6|>>;\n"
                        + "    70: \u96f6; \u96f6|>>;\n"
                        + "    80: \u96f6; \u96f6|>>;\n"
                        + "    90: \u96f6; \u96f6|>>;\n"
                        + "    100: <%simplified< \u96f6; <%simplified< \u96f6 >>;\n"
                        + "    1000: <%simplified< \u96f6; <%simplified< \u96f6 >>;\n"
                        + "    1,000,000: <%simplified< \u96f6; <%simplified< \u96f6 >>;\n"
                        + "    1,000,000,000,000: <%simplified< \u96f6;\n"
                        + "        <%simplified< \u96f6 >>;\n"
                        + "    1,000,000,000,000,000: =#,##0=;"
                        + "%default:\n"
                        + "    -x: minus >>;\n"
                        + "    x.x: << point >>;\n"
                        + "    =%simplified=;\n"
                        + "    100: << hundred[ >%%and>];\n"
                        + "    1000: << thousand[ >%%and>];\n"
                        + "    100,000>>: << thousand[>%%commas>];\n"
                        + "    1,000,000: << million[>%%commas>];\n"
                        + "    1,000,000,000,000: << billion[>%%commas>];\n"
                        + "    1,000,000,000,000,000: =#,##0=;\n"
                        + "%%and:\n"
                        + "    and =%default=;\n"
                        + "    100: =%default=;\n"
                        + "%%commas:\n"
                        + "    ' and =%default=;\n"
                        + "    100: , =%default=;\n"
                        + "    1000: , <%default< thousand, >%default>;\n"
                        + "    1,000,000: , =%default=;"
                        + "%traditional:\n"
                        + "    -x: \u3007| >>;\n"
                        + "    x.x: << \u9ede >>;\n"
                        + "    \u842c; \u842c; \u842c; \u842c; \u842c; \u842c; \u842c; \u842c; \u842c; \u842c;\n"
                        + "    \u842c; \u842c; \u842c; \u842c; \u842c; \u842c; \u842c;\n"
                        + "        \u842c; \u842c; \u842c;\n"
                        + "    20: \u842c[->>];\n"
                        + "    30: \u842c[->>];\n"
                        + "    40: \u842c[->>];\n"
                        + "    50: \u842c[->>];\n"
                        + "    60: \u842c[->>];\n"
                        + "    70: \u842c[->>];\n"
                        + "    80: \u842c[->>];\n"
                        + "    90: \u842c[->>];\n"
                        + "    100: << \u842c[ >>];\n"
                        + "    1000: << \u842c[ >>];\n"
                        + "    1,000,000: << \u842c[ >>];\n"
                        + "    1,000,000,000,000: << \u842c[ >>];\n"
                        + "    1,000,000,000,000,000: =#,##0=;\n"
                        + "%time:\n"
                        + "    =0= sec.;\n"
                        + "    60: =%%min-sec=;\n"
                        + "    3600: =%%hr-min-sec=;\n"
                        + "%%min-sec:\n"
                        + "    0: *=00=;\n"
                        + "    60/60: <0<>>;\n"
                        + "%%hr-min-sec:\n"
                        + "    0: *=00=;\n"
                        + "    60/60: <00<>>;\n"
                        + "    3600/60: <#,##0<:>>>;\n"
                        + "%%post-process:com.ibm.icu.text.RBNFChinesePostProcessor\n";

        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ruleWithChinese, ULocale.CHINESE);
        String[] ruleNames = rbnf.getRuleSetNames();
        try {
            // Test with "null" rules
            rbnf.format(0.0, null);
            errln("This was suppose to return an exception for a null format");
        } catch (Exception e) {
        }
        for (int i = 0; i < ruleNames.length; i++) {
            try {
                rbnf.format(-123450.6789, ruleNames[i]);
            } catch (Exception e) {
                errln(
                        "RBNFChinesePostProcessor was not suppose to return an exception "
                                + "when being formatted with parameters 0.0 and "
                                + ruleNames[i]);
            }
        }
    }

    @Test
    public void TestSetDecimalFormatSymbols() {
        RuleBasedNumberFormat rbnf =
                new RuleBasedNumberFormat(Locale.ENGLISH, RuleBasedNumberFormat.ORDINAL);

        DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.ENGLISH);

        double number = 1001;

        String[] expected = {"1,001st", "1&001st"};

        String result = rbnf.format(number);
        if (!result.equals(expected[0])) {
            errln("Format Error - Got: " + result + " Expected: " + expected[0]);
        }

        /* Set new symbol for testing */
        dfs.setGroupingSeparator('&');
        rbnf.setDecimalFormatSymbols(dfs);

        result = rbnf.format(number);
        if (!result.equals(expected[1])) {
            errln("Format Error - Got: " + result + " Expected: " + expected[1]);
        }
    }

    @Test
    public void TestContext() {
        class TextContextItem {
            public String locale;
            public int format;
            public DisplayContext context;
            public double value;
            public String expectedResult;

            // Simple constructor
            public TextContextItem(
                    String loc, int fmt, DisplayContext ctxt, double val, String expRes) {
                locale = loc;
                format = fmt;
                context = ctxt;
                value = val;
                expectedResult = expRes;
            }
        }
        final TextContextItem[] items = {
            new TextContextItem(
                    "sv",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,
                    123.45,
                    "ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem"),
            new TextContextItem(
                    "sv",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE,
                    123.45,
                    "Ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem"),
            new TextContextItem(
                    "sv",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU,
                    123.45,
                    "ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem"),
            new TextContextItem(
                    "sv",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_STANDALONE,
                    123.45,
                    "ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem"),
            new TextContextItem(
                    "en",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,
                    123.45,
                    "one hundred twenty-three point four five"),
            new TextContextItem(
                    "en",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE,
                    123.45,
                    "One hundred twenty-three point four five"),
            new TextContextItem(
                    "en",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU,
                    123.45,
                    "One hundred twenty-three point four five"),
            new TextContextItem(
                    "en",
                    RuleBasedNumberFormat.SPELLOUT,
                    DisplayContext.CAPITALIZATION_FOR_STANDALONE,
                    123.45,
                    "One hundred twenty-three point four five"),
        };
        for (TextContextItem item : items) {
            ULocale locale = new ULocale(item.locale);
            RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(locale, item.format);
            rbnf.setContext(item.context);
            String result = rbnf.format(item.value, rbnf.getDefaultRuleSetName());
            if (!result.equals(item.expectedResult)) {
                errln(
                        "Error for locale "
                                + item.locale
                                + ", context "
                                + item.context
                                + ", expected "
                                + item.expectedResult
                                + ", got "
                                + result);
            }
            RuleBasedNumberFormat rbnfClone = rbnf.clone();
            if (!rbnfClone.equals(rbnf)) {
                errln(
                        "Error for locale "
                                + item.locale
                                + ", context "
                                + item.context
                                + ", rbnf.clone() != rbnf");
            } else {
                result = rbnfClone.format(item.value, rbnfClone.getDefaultRuleSetName());
                if (!result.equals(item.expectedResult)) {
                    errln(
                            "Error with clone for locale "
                                    + item.locale
                                    + ", context "
                                    + item.context
                                    + ", expected "
                                    + item.expectedResult
                                    + ", got "
                                    + result);
                }
            }
        }
    }

    @Test
    public void TestInfinityNaN() {
        String enRules =
                "%default:"
                        + "-x: minus >>;"
                        + "Inf: infinite;"
                        + "NaN: not a number;"
                        + "0: =#,##0=;";
        RuleBasedNumberFormat enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
        String[][] enTestData = {
            {"1", "1"},
            {"\u221E", "infinite"},
            {"-\u221E", "minus infinite"},
            {"NaN", "not a number"},
        };

        doTest(enFormatter, enTestData, true);

        // Test the default behavior when the rules are undefined.
        enRules = "%default:" + "-x: ->>;" + "0: =#,##0=;";
        enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
        String[][] enDefaultTestData = {
            {"1", "1"},
            {"\u221E", "∞"},
            {"-\u221E", "-∞"},
            {"NaN", "NaN"},
        };

        doTest(enFormatter, enDefaultTestData, true);
    }

    @Test
    public void TestVariableDecimalPoint() {
        String enRules =
                "%spellout-numbering:"
                        + "-x: minus >>;"
                        + "x.x: << point >>;"
                        + "x,x: << comma >>;"
                        + "0.x: xpoint >>;"
                        + "0,x: xcomma >>;"
                        + "0: zero;"
                        + "1: one;"
                        + "2: two;"
                        + "3: three;"
                        + "4: four;"
                        + "5: five;"
                        + "6: six;"
                        + "7: seven;"
                        + "8: eight;"
                        + "9: nine;";
        RuleBasedNumberFormat enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
        String[][] enTestPointData = {
            {"1.1", "one point one"},
            {"1.23", "one point two three"},
            {"0.4", "xpoint four"},
        };
        doTest(enFormatter, enTestPointData, true);
        DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(ULocale.ENGLISH);
        decimalFormatSymbols.setDecimalSeparator(',');
        enFormatter.setDecimalFormatSymbols(decimalFormatSymbols);
        String[][] enTestCommaData = {
            {"1.1", "one comma one"},
            {"1.23", "one comma two three"},
            {"0.4", "xcomma four"},
        };
        doTest(enFormatter, enTestCommaData, true);
    }

    @Test
    public void TestRounding() {
        RuleBasedNumberFormat enFormatter =
                new RuleBasedNumberFormat(ULocale.ENGLISH, RuleBasedNumberFormat.SPELLOUT);
        String[][] enTestFullData = {
            {"0", "zero"},
            {"0.4", "zero point four"},
            {"0.49", "zero point four nine"},
            {"0.5", "zero point five"},
            {"0.51", "zero point five one"},
            {"0.99", "zero point nine nine"},
            {"1", "one"},
            {"1.01", "one point zero one"},
            {"1.49", "one point four nine"},
            {"1.5", "one point five"},
            {"1.51", "one point five one"},
            {
                "450359962737049.6",
                "four hundred fifty trillion three hundred fifty-nine billion nine hundred sixty-two million seven hundred thirty-seven thousand forty-nine point six"
            }, // 2^52 / 10
            {
                "450359962737049.7",
                "four hundred fifty trillion three hundred fifty-nine billion nine hundred sixty-two million seven hundred thirty-seven thousand forty-nine point seven"
            }, // 2^52 + 1 / 10
        };
        doTest(enFormatter, enTestFullData, false);

        enFormatter.setMaximumFractionDigits(0);
        enFormatter.setRoundingMode(BigDecimal.ROUND_HALF_EVEN);
        String[][] enTestIntegerData = {
            {"0", "zero"},
            {"0.4", "zero"},
            {"0.49", "zero"},
            {"0.5", "zero"},
            {"0.51", "one"},
            {"0.99", "one"},
            {"1", "one"},
            {"1.01", "one"},
            {"1.49", "one"},
            {"1.5", "two"},
            {"1.51", "two"},
        };
        doTest(enFormatter, enTestIntegerData, false);

        enFormatter.setMaximumFractionDigits(1);
        enFormatter.setRoundingMode(BigDecimal.ROUND_HALF_EVEN);
        String[][] enTestTwoDigitsData = {
            {"0", "zero"},
            {"0.04", "zero"},
            {"0.049", "zero"},
            {"0.05", "zero"},
            {"0.051", "zero point one"},
            {"0.099", "zero point one"},
            {"10.11", "ten point one"},
            {"10.149", "ten point one"},
            {"10.15", "ten point two"},
            {"10.151", "ten point two"},
        };
        doTest(enFormatter, enTestTwoDigitsData, false);

        enFormatter.setMaximumFractionDigits(3);
        enFormatter.setRoundingMode(BigDecimal.ROUND_DOWN);
        String[][] enTestThreeDigitsDownData = {
            {"4.3", "four point three"}, // Not 4.299!
        };
        doTest(enFormatter, enTestThreeDigitsDownData, false);
    }

    @Test
    public void testLargeNumbers() {
        RuleBasedNumberFormat rbnf =
                new RuleBasedNumberFormat(ULocale.US, RuleBasedNumberFormat.SPELLOUT);

        String[][] enTestFullData = {
            {
                "-9007199254740991",
                "minus nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-one"
            }, // Maximum precision in both a double and a long
            {
                "9007199254740991",
                "nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-one"
            }, // Maximum precision in both a double and a long
            {
                "-9007199254740992",
                "minus nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-two"
            }, // Only precisely contained in a long
            {
                "9007199254740992",
                "nine quadrillion seven trillion one hundred ninety-nine billion two hundred fifty-four million seven hundred forty thousand nine hundred ninety-two"
            }, // Only precisely contained in a long
            {
                "9999999999999998",
                "nine quadrillion nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-eight"
            },
            {
                "9999999999999999",
                "nine quadrillion nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine"
            },
            {
                "999999999999999999",
                "nine hundred ninety-nine quadrillion nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine"
            },
            {
                "1000000000000000000", "1,000,000,000,000,000,000"
            }, // The rules don't go to 1 quintillion yet
            {
                "-9223372036854775809", "-9,223,372,036,854,775,809"
            }, // We've gone beyond 64-bit precision
            {
                "-9223372036854775808", "-9,223,372,036,854,775,808"
            }, // We've gone beyond +64-bit precision
            {"-9223372036854775807", "minus 9,223,372,036,854,775,807"}, // Minimum 64-bit precision
            {
                "-9223372036854775806", "minus 9,223,372,036,854,775,806"
            }, // Minimum 64-bit precision + 1
            {"9223372036854774111", "9,223,372,036,854,774,111"}, // Below 64-bit precision
            {"9223372036854774999", "9,223,372,036,854,774,999"}, // Below 64-bit precision
            {"9223372036854775000", "9,223,372,036,854,775,000"}, // Below 64-bit precision
            {"9223372036854775806", "9,223,372,036,854,775,806"}, // Maximum 64-bit precision - 1
            {"9223372036854775807", "9,223,372,036,854,775,807"}, // Maximum 64-bit precision
            {
                "9223372036854775808", "9,223,372,036,854,775,808"
            }, // We've gone beyond 64-bit precision. This can only be represented with BigDecimal.
        };
        doTest(rbnf, enTestFullData, false);
    }

    @Test
    public void testCompactDecimalFormatStyle() {
        // This is not a common use case, but we're testing it anyway.
        final String numberPattern =
                "=###0.#####=;"
                        + "1000: <###0.00< K;"
                        + "1000000: <###0.00< M;"
                        + "1000000000: <###0.00< B;"
                        + "1000000000000: <###0.00< T;"
                        + "1000000000000000: <###0.00< Q;";
        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(numberPattern, ULocale.US);

        String[][] enTestFullData = {
            {"1000", "1.00 K"},
            {"1234", "1.23 K"},
            {"999994", "999.99 K"},
            {"999995", "1000.00 K"},
            {"1000000", "1.00 M"},
            {"1200000", "1.20 M"},
            {"1200000000", "1.20 B"},
            {"1200000000000", "1.20 T"},
            {"1200000000000000", "1.20 Q"},
            {"4503599627370495", "4.50 Q"},
            {"4503599627370496", "4.50 Q"},
            {"8990000000000000", "8.99 Q"},
            {"9008000000000000", "9.00 Q"}, // Number doesn't precisely fit into a double
            {"9456000000000000", "9.00 Q"}, // Number doesn't precisely fit into a double
            {"10000000000000000", "10.00 Q"}, // Number doesn't precisely fit into a double
            {"9223372036854775807", "9223.00 Q"}, // Maximum 64-bit precision
            {
                "9223372036854775808", "9,223,372,036,854,775,808"
            }, // We've gone beyond 64-bit precision. This can only be represented with BigDecimal.
        };
        doTest(rbnf, enTestFullData, false);
    }

    private void assertEquals(String expected, String result) {
        if (!expected.equals(result)) {
            errln("Expected: " + expected + " Got: " + result);
        }
    }

    @Test
    public void testRoundingUnrealNumbers() {
        RuleBasedNumberFormat rbnf =
                new RuleBasedNumberFormat(ULocale.US, RuleBasedNumberFormat.SPELLOUT);
        rbnf.setRoundingMode(BigDecimal.ROUND_HALF_UP);
        rbnf.setMaximumFractionDigits(3);
        assertEquals("zero point one", rbnf.format(0.1));
        assertEquals("zero point zero zero one", rbnf.format(0.0005));
        assertEquals("infinity", rbnf.format(Double.POSITIVE_INFINITY));
        assertEquals("not a number", rbnf.format(Double.NaN));
    }

    @Test
    public void TestNumberingSystem() {
        RuleBasedNumberFormat rbnf =
                new RuleBasedNumberFormat(ULocale.US, RuleBasedNumberFormat.NUMBERING_SYSTEM);

        assertEquals("Wrong result with default rule set", "123", rbnf.format(123));

        rbnf.setDefaultRuleSet("%ethiopic");
        assertEquals("Wrong result with Ethiopic rule set", "፻፳፫", rbnf.format(123));
    }

    @Test
    public void TestInfiniteRecursion() {
        final String[] badRules = {
            ">>", "<<", "<<<", ">>>", "%foo: x=%foo=", "%one: x>%two>; %two: y>%one>;"
        };

        for (String badRule : badRules) {
            try {
                RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(badRule);
                try {
                    rbnf.format(5);
                    // we don't actually care about the result; we just want to make sure the
                    // function returns
                } catch (IllegalStateException e) {
                    // we're supposed to get an IllegalStateException; swallow it and continue
                }

                try {
                    rbnf.parse("foo");
                    errln("Parse test didn't throw an exception!");
                } catch (IllegalStateException e) {
                    // we're supposed to get an IllegalStateException; swallow it and continue
                } catch (ParseException e) {
                    // if we don't hit the recursion limit, we'll still fail to parse the number,
                    // so also swallow this exception and continue
                }
            } catch (IllegalArgumentException e) {
                // ">>>" generates a parse exception when you try to create the formatter (so we
                // expect that rather
                // than re-throwing and triggering a test failure)
                // (eventually it'd be nice to statically analyze the rules for (at least) the most
                // common
                // causes of infinite recursion, in which case we'd end up down here and need to
                // check
                // the error code.  But for now, we probably won't end up here and don't care if we
                // do)
                if (!badRule.equals("<<<")) {
                    throw e;
                }
            }
        }
    }

    /**
     * This test is a little contrived for English, but the grammar is relevant for several
     * languages, including: Latin, Germanic, Slavic and Indic. It's pretty common, especially for
     * ordinals, to use different words as a magnitude unit and when it's the final word. Several
     * languages need grammatical agreement between the final and non-final magnitude unit with the
     * numerical quantity before the unit. This test is the equivalent seen in other languages.
     */
    @Test
    public void testOmissionReplacementWithPluralRules() {
        final String rules =
                "%cardinal:\n"
                        + "-x: minus >>;\n"
                        + "x.x: << point >>;\n"
                        + "Inf: infinite;\n"
                        + "NaN: not a number;\n"
                        + "zero; one; two; three; four; five; six; seven; eight; nine;\n"
                        + "ten; eleven; twelve; thirteen; fourteen; fifteen; sixteen; seventeen; eighteen; nineteen;\n"
                        + "20: twenty[->>];\n"
                        + "30: thirty[->>];\n"
                        + "40: forty[->>];\n"
                        + "50: fifty[->>];\n"
                        + "60: sixty[->>];\n"
                        + "70: seventy[->>];\n"
                        + "80: eighty[->>];\n"
                        + "90: ninety[->>];\n"
                        + "100: << hundred[ >>];\n"
                        + "1000: << thousand[ >>];\n"
                        + "1000000: << million[ >>];\n"
                        + "1000000000: << billion[ >>];\n"
                        + "1000000000000: << trillion[ >>];\n"
                        + "1000000000000000: =#,##0=;\n"
                        + "%ordinal:\n"
                        + "-x: minus >>;\n"
                        + "x.x: =#,##0.#=;\n"
                        + "Inf: infinitieth;\n"
                        + "zeroth; first; second; third; fourth; fifth; sixth; seventh; eighth; ninth;\n"
                        + "tenth; eleventh; twelfth;\n"
                        + "13: =%cardinal=th;\n"
                        + "20: twent[y->>|ieth];\n"
                        + "30: thirt[y->>|ieth];\n"
                        + "40: fort[y->>|ieth];\n"
                        + "50: fift[y->>|ieth];\n"
                        + "60: sixt[y->>|ieth];\n"
                        + "70: sevent[y->>|ieth];\n"
                        + "80: eight[y->>|ieth];\n"
                        + "90: ninet[y->>|ieth];\n"
                        + "100: <%cardinal< [$(cardinal,one{hundred}other{hundreds})$ >>|$(cardinal,one{hundredth}other{hundredths})$];\n"
                        + "1000: <%cardinal< [$(cardinal,one{thousand}other{thousands})$ >>|$(cardinal,one{thousandth}other{thousandths})$];\n"
                        + "1000000: <%cardinal< [$(cardinal,one{million}other{millions})$ >>|$(cardinal,one{millionth}other{millionths})$];\n"
                        + "1000000000: <%cardinal< [$(cardinal,one{billion}other{billions})$ >>|$(cardinal,one{billionth}other{billionths})$];\n"
                        + "1000000000000: <%cardinal< [$(cardinal,one{trillion}other{trillions})$ >>|$(cardinal,one{trillionth}other{trillionths})$];\n"
                        + "1000000000000000: =#,##0=$(ordinal,one{st}two{nd}few{rd}other{th})$;";
        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(rules, ULocale.US);

        String[][] enTestFullData = {
            {"20", "twentieth"},
            {"21", "twenty-first"},
            {"29", "twenty-ninth"},
            {"30", "thirtieth"},
            {"31", "thirty-first"},
            {"39", "thirty-ninth"},
            {"100", "one hundredth"},
            {"101", "one hundred first"},
            {"200", "two hundredths"},
            {"201", "two hundreds first"},
            {"300", "three hundredths"},
            {"301", "three hundreds first"},
            {"1000", "one thousandth"},
            {"1001", "one thousand first"},
            {"1100", "one thousand one hundredth"},
            {"1101", "one thousand one hundred first"},
            {"1200", "one thousand two hundredths"},
            {"1201", "one thousand two hundreds first"},
            {"2000", "two thousandths"},
            {"2001", "two thousands first"},
            {"2100", "two thousands one hundredth"},
            {"2101", "two thousands one hundred first"},
            {"8000", "eight thousandths"},
            {"8001", "eight thousands first"},
            {"888000", "eight hundred eighty-eight thousandths"},
            {"888001", "eight hundred eighty-eight thousands first"},
            {"888100", "eight hundred eighty-eight thousands one hundredth"},
            {"999101", "nine hundred ninety-nine thousands one hundred first"},
            {"999200", "nine hundred ninety-nine thousands two hundredths"},
            {"999201", "nine hundred ninety-nine thousands two hundreds first"},
        };
        doTest(rbnf, enTestFullData, false);
    }

    /*
     * Using << twice, or >> twice, or == twice is ambiguous for parsing the real value.
     * While the results may sometimes format correctly, it is too ambiguous to parse.
     * Is the first or second substitution the correct value?
     * Newer plural rules and the orElse operator made this kind of syntax unnecessary, but
     * some people may have previously developed rules that used this kind of workaround.
     */
    @Test
    public void TestUnparseableConflictingSubstitutions() {
        RuleBasedNumberFormat rbnf =
                new RuleBasedNumberFormat("<<✧<<✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧", Locale.US);
        try {
            rbnf.parse("✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧✧");
            errln("Failed to throw exception");
        } catch (IllegalArgumentException | ParseException e) {
            // Works as expected
        }
    }

    @Test
    public void TestAmbiguousDelimiter() throws ParseException {
        RuleBasedNumberFormat rbnf =
                new RuleBasedNumberFormat("%default:\n0: =#,##0=.;", Locale.GERMANY);

        Number result = rbnf.parse("10.000.000.");
        if (result.longValue() != 10000000) {
            // It better not be 10!
            errln("parse got " + result.longValue() + " instead of 10000000");
        }
    }
}
